﻿// (c) Copyright Microsoft Corporation.
// This source is subject to [###LICENSE_NAME###].
// Please see [###LICENSE_LINK###] for details.
// All other rights reserved.

using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace EnsureWeb.Controls
{
    /// <summary>
    /// Timeline control allow user to pick up a date or select a period of time. 
    /// </summary>
    /// <remarks>
    /// <para>
    /// Date on Timeline arrange in a horizontal straight line and are grouped 
    /// by different time dimensions in Decade View, Year View, Month View or 
    /// Day View. User can easily navigate between different time dimension by
    /// drilling down or rolling up. In addition, within the time dimension, 
    /// selected time can be automatically changed at interval of the current 
    /// time dimension like decade or year. This continuous invoking at interval
    /// can be used for animation over time.
    /// </para>
    /// </remarks>
    [TemplatePart(Name = MultiScaleTimeline.ElementPlayButton, Type = typeof(ToggleButton))]
    [TemplatePart(Name = MultiScaleTimeline.ElementSelectionBar, Type = typeof(Path))]
    [TemplatePart(Name = MultiScaleTimeline.ElementTimelineItemPanel, Type = typeof(Grid))]
    [TemplatePart(Name = MultiScaleTimeline.ElementTimelineMarkPanel, Type = typeof(Canvas))]
    [TemplatePart(Name = MultiScaleTimeline.ElementRollupButton, Type = typeof(Button))]
    [TemplatePart(Name = MultiScaleTimeline.ElementLeftButton, Type = typeof(RepeatButton))]
    [TemplatePart(Name = MultiScaleTimeline.ElementRightButton, Type = typeof(RepeatButton))]
    public class MultiScaleTimeline : Control
    {
        #region Template part and style names

        /// <summary>
        /// The PlayButton Template UI Element.
        /// </summary>
        private const string ElementPlayButton = "PlayButton";

        /// <summary>
        /// The SelectionBar Template UI Element.
        /// </summary>
        private const string ElementSelectionBar = "SelectionBar";

        /// <summary>
        /// The TimelineItemPanel Template UI Element.
        /// </summary>
        private const string ElementTimelineItemPanel = "TimelineItemPanel";

        /// <summary>
        /// The TimelineMarkPanel Template UI Element.
        /// </summary>
        private const string ElementTimelineMarkPanel = "TimelineMarkPanel";

        /// <summary>
        /// The UI element of RollupButton.
        /// </summary>
        private const string ElementRollupButton = "RollupButton";

        /// <summary>
        /// The UI element of LeftButton.
        /// </summary>
        private const string ElementLeftButton = "LeftButton";

        /// <summary>
        /// The UI element of RightButton.
        /// </summary>
        private const string ElementRightButton = "RightButton";

        /// <summary>
        /// The UI element of ShowTextStoryboard.
        /// </summary>
        private const string ElementShowTextStoryboard = "ShowTextStoryboard";
        #endregion

        /// <summary>
        /// The element of play and pause button, checked means in play state.
        /// </summary>
        private ToggleButton PlayButton;

        /// <summary>
        /// The TimelineItemPanel.
        /// </summary>
        private Grid TimelineItemPanel;

        /// <summary>
        /// The TimelineMarkPanel.
        /// </summary>
        private Canvas TimelineMarkPanel;

        /// <summary>
        /// The rollup button.
        /// </summary>
        private Button RollupButton;

        /// <summary>
        /// The button for user navigate to previous time range .
        /// </summary>
        private RepeatButton LeftButton;

        /// <summary>
        /// The button for user navigate to next time range .
        /// </summary>
        private RepeatButton RightButton;

        /// <summary>
        /// Current instance of timeview.
        /// </summary>
        private TimeView CurrentView;

        /// <summary>
        /// Timer used by play animation.
        /// </summary>
        private DispatcherTimer PlayTimer;

        internal bool IsSelectableDateRangeValide = true;

        #region public ObservableCollection<UIElement> TimelineMarks
        /// <summary>
        /// Gets or sets the list or timeline marks.
        /// </summary>
        public ObservableCollection<UIElement> TimelineMarks
        {
            get { return (ObservableCollection<UIElement>)GetValue(TimelineMarksProperty); }
            set { SetValue(TimelineMarksProperty, value); }
        }

        /// <summary>
        /// TimelineMarks dependency property.
        /// </summary>
        public static readonly DependencyProperty TimelineMarksProperty =
            DependencyProperty.Register("TimelineMarks", typeof(ObservableCollection<UIElement>), typeof(MultiScaleTimeline), new PropertyMetadata(OnTimelineMarksChanged));

        /// <summary>
        /// Execute when TimelineMarks property changed.
        /// </summary>
        /// <param name="d">Event Source.</param>
        /// <param name="e">Event args.</param>
        private static void OnTimelineMarksChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MultiScaleTimeline control = d as MultiScaleTimeline;
            if (control != null)
            {
                control.ArrangeMarks();
            }
        }
        #endregion

        #region public bool ShowPlayButton

        /// <summary>
        /// Gets or sets a value indicating whether or not the play button is shown.
        /// </summary>
        public bool ShowPlayButton
        {
            get { return (bool)GetValue(ShowPlayButtonProperty); }
            set { SetValue(ShowPlayButtonProperty, value); }
        }

        /// <summary>
        /// Is show play button dependency property.
        /// </summary>
        public static readonly DependencyProperty ShowPlayButtonProperty =
            DependencyProperty.Register("ShowPlayButton", typeof(bool), typeof(MultiScaleTimeline), new PropertyMetadata(true, new PropertyChangedCallback(OnShowButtonPropertyChanged)));

        /// <summary>
        /// Execute when isshowbutton property changed.
        /// </summary>
        /// <param name="d">Event Source.</param>
        /// <param name="e">Event args.</param>
        private static void OnShowButtonPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MultiScaleTimeline control = d as MultiScaleTimeline;
            bool value = (bool)e.NewValue;
            if (control != null)
            {
                control.OnShowButtonPropertyChanged(value);
            }
        }

        /// <summary>
        /// Execute when isShowButton property changed.
        /// </summary>
        /// <param name="value">New value.</param>
        private void OnShowButtonPropertyChanged(bool value)
        {
            if (this.PlayButton != null)
            {
                this.PlayButton.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
            }
        }
        #endregion

        #region public Style TimelineItemStyle
        /// <summary>
        /// Gets or sets the property of TimelineItemStyle.
        /// </summary>
        public Style TimelineItemStyle
        {
            get { return (Style)GetValue(TimelineItemStyleProperty); }
            set { SetValue(TimelineItemStyleProperty, value); }
        }

        /// <summary>
        /// The dependency property of TimelineItemStyle.
        /// </summary>
        public static readonly DependencyProperty TimelineItemStyleProperty =
            DependencyProperty.Register("TimelineItemStyle", typeof(Style), typeof(MultiScaleTimeline), new PropertyMetadata(null));
        #endregion

        #region public TimelineScale MinimumScale
        /// <summary>
        /// Gets or sets the minimumScale. The default value is TimelineScale.Day.
        /// </summary>
        public TimelineScale MinimumScale
        {
            get { return (TimelineScale)GetValue(MinimumScaleProperty); }
            set { SetValue(MinimumScaleProperty, value); }
        }

        /// <summary>
        /// Identifies the MinimumScale dependency property.
        /// </summary>
        public static readonly DependencyProperty MinimumScaleProperty =
            DependencyProperty.Register("MinimumScale", typeof(TimelineScale), typeof(MultiScaleTimeline), new PropertyMetadata(TimelineScale.Second, OnMinimumScaleChanged));

        /// <summary>
        /// Excute when the MinimumScale property changed.
        /// </summary>
        /// <param name="d">Event source.</param>
        /// <param name="e">Event args.</param>
        private static void OnMinimumScaleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MultiScaleTimeline control = d as MultiScaleTimeline;
            TimelineScale value = (TimelineScale)e.NewValue;

            if (control != null)
            {
                TimelineScale maxScale = control.MaximumScale;
                TimelineScale curScale = control.Scale;
                int maxValue = Convert.ToInt16(maxScale, CultureInfo.InvariantCulture);
                int minValue = Convert.ToInt16(value, CultureInfo.InvariantCulture);
                int curValue = Convert.ToInt16(curScale, CultureInfo.InvariantCulture);
                if (minValue < maxValue)
                {
                    throw new ArgumentOutOfRangeException("e", "MinimumScale should not larger than MaximumScale");
                }

                if (minValue <= curValue)
                {
                    control.UpdateVisual();
                }
            }
        }

        #endregion

        #region public TimelineScale MaximumScale
        /// <summary>
        /// Gets or sets the manimumScale. The default value is TimelineScale.Decade.
        /// </summary>
        public TimelineScale MaximumScale
        {
            get { return (TimelineScale)GetValue(MaximumScaleProperty); }
            set { SetValue(MaximumScaleProperty, value); }
        }

        /// <summary>
        /// Identifies the MaximumScale dependency property.
        /// </summary>
        public static readonly DependencyProperty MaximumScaleProperty =
            DependencyProperty.Register("MaximumScale", typeof(TimelineScale), typeof(MultiScaleTimeline), new PropertyMetadata(TimelineScale.Decade, OnMaximumScaleChanged));

        /// <summary>
        /// Excute when MaximumScale property changed.
        /// </summary>
        /// <param name="d">Event source.</param>
        /// <param name="e">Event args.</param>
        private static void OnMaximumScaleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MultiScaleTimeline control = d as MultiScaleTimeline;
            TimelineScale value = (TimelineScale)e.NewValue;
            if (control != null)
            {
                TimelineScale minScale = control.MinimumScale;
                TimelineScale curScale = control.Scale;
                int minValue = Convert.ToInt16(minScale, CultureInfo.InvariantCulture);
                int maxValue = Convert.ToInt16(value, CultureInfo.InvariantCulture);
                int curValue = Convert.ToInt16(curScale, CultureInfo.InvariantCulture);

                if (maxValue > minValue)
                {
                    throw new ArgumentOutOfRangeException("e", "MaximumScale should not less than MinimumScale");
                }

                if (maxValue >= curValue)
                {
                    control.UpdateVisual();
                }
            }
        }
        #endregion

        #region public DateTime? SelectableDateStart
        /// <summary>
        /// Gets or sets the first date that can be selected.
        /// </summary>
        [TypeConverter(typeof(DateTimeConverter))]
        public DateTime? SelectableDateStart
        {
            get { return (DateTime?)GetValue(SelectableDateStartProperty); }
            set { SetValue(SelectableDateStartProperty, value); }
        }

        /// <summary>
        /// THe selectable date start dependency property.
        /// </summary>
        public static readonly DependencyProperty SelectableDateStartProperty =
            DependencyProperty.Register("SelectableDateStart", typeof(DateTime?), typeof(MultiScaleTimeline), new PropertyMetadata(OnSelectaleDateStartChanged));

        private static void OnSelectaleDateStartChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MultiScaleTimeline control = d as MultiScaleTimeline;
            DateTime value = (DateTime)e.NewValue;
            if (control != null)
            {
                //if (control.SelectableDateEnd.HasValue && value > control.SelectableDateEnd)
                //{
                //    throw new ArgumentOutOfRangeException("e", "SelectableDateStart should not later then SelectableDateEnd");
                //}
                control.CheckSelectableDateRangeValide();
                control.UpdateVisual();

            }
        }


        #endregion

        #region public DateTime? SelectableDateEnd
        /// <summary>
        /// Gets or sets the last date that can be selected.
        /// </summary>
        [TypeConverter(typeof(DateTimeConverter))]
        public DateTime? SelectableDateEnd
        {
            get { return (DateTime?)GetValue(SelectableDateEndProperty); }
            set { SetValue(SelectableDateEndProperty, value); }
        }

        /// <summary>
        /// The selectable date end dependency property.
        /// </summary>
        public static readonly DependencyProperty SelectableDateEndProperty =
            DependencyProperty.Register("SelectableDateEnd", typeof(DateTime?), typeof(MultiScaleTimeline), new PropertyMetadata(OnSelectaleDateEndChanged));

        private static void OnSelectaleDateEndChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MultiScaleTimeline control = d as MultiScaleTimeline;
            DateTime value = (DateTime)e.NewValue;
            if (control != null)
            {
                //if (control.SelectableDateStart.HasValue && value < control.SelectableDateStart)
                //{
                //    throw new ArgumentOutOfRangeException("e", "SelectableDateEnd should not earlier then SelectableDateStart");
                //}
                control.CheckSelectableDateRangeValide();
                control.UpdateVisual();
            }
        }

        #endregion

        #region Attached Property MarkDate
        /// <summary>
        /// Gets the value of the MarkDate attached property to a specified element.
        /// </summary>
        /// <param name="element">The element from which the property value is read.</param>
        /// <returns>The MarkDate property value for the element.</returns>
        public static DateTime GetMarkDate(DependencyObject element)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }

            return (DateTime)element.GetValue(MarkDateProperty);
        }

        /// <summary>
        /// Sets the value of the MarkDate attached property to a specified element.
        /// </summary>
        /// <param name="element">The element to which the attached property is written.</param>
        /// <param name="time">The needed MarkDate value.</param>
        public static void SetMarkDate(DependencyObject element, DateTime time)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }

            element.SetValue(MarkDateProperty, time);
        }

        /// <summary>
        /// Identifies the MarkDate dependency property.
        /// </summary>
        public static readonly DependencyProperty MarkDateProperty =
            DependencyProperty.RegisterAttached(
            "MarkDate",
            typeof(DateTime),
            typeof(MultiScaleTimeline),
            new PropertyMetadata(DateTime.Now, OnMarkDateChanged));

        /// <summary>
        /// MarkDateProperty property changed handler.
        /// </summary>
        /// <param name="d">UIElement that changed its MarkDate.</param>
        /// <param name="e">Event arguments.</param>
        private static void OnMarkDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
        }

        #endregion

        #region public TimelineScale Scale

        /// <summary>
        /// Gets or sets a value indicated the deepest display mode can be reached in months, years, or decades. 
        /// for example, if value is TimelineMode.Year, user can reach decade view and year view , but can't reach month view or day view.
        /// </summary>
        public TimelineScale Scale
        {
            get { return (TimelineScale)GetValue(ScaleProperty); }
            set { SetValue(ScaleProperty, value); }
        }

        /// <summary>
        /// Identifies the DisplayModeDepth dependency property.
        /// </summary>
        public static readonly DependencyProperty ScaleProperty =
            DependencyProperty.Register("Scale", typeof(TimelineScale), typeof(MultiScaleTimeline), new PropertyMetadata(TimelineScale.Year, OnScaleChanged));

        /// <summary>
        /// Execute when scale changed.
        /// </summary>
        /// <param name="d">Event source.</param>
        /// <param name="e">Event args.</param>
        private static void OnScaleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MultiScaleTimeline timeline = d as MultiScaleTimeline;

            TimelineScale oldScale = (TimelineScale)e.OldValue;
            TimelineScale newScale = (TimelineScale)e.NewValue;

            if (timeline != null)
            {
                timeline.UpdateScale(newScale);
            }
        }
        #endregion

        #region public DateTime SelectedDate
        /// <summary>
        /// Gets or sets the currently selected date. 
        /// </summary>
        [TypeConverter(typeof(DateTimeConverter))]
        public DateTime SelectedDate
        {
            get { return (DateTime)GetValue(SelectedDateProperty); }
            set { SetValue(SelectedDateProperty, value); }
        }

        /// <summary>
        /// The selected date dependency property.
        /// </summary>
        public static readonly DependencyProperty SelectedDateProperty =
            DependencyProperty.Register("SelectedDate", typeof(DateTime), typeof(MultiScaleTimeline), new PropertyMetadata(DateTime.Now, OnSelectedDateChanged));

        private static void OnSelectedDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MultiScaleTimeline control = d as MultiScaleTimeline;
            DateTime value = (DateTime)e.NewValue;
            if (control != null)
            {
                control.SetSelectionState();
            }
        }

        #endregion

        #region public TimelineSelectionMode SelectionMode

        /// <summary>
        /// Gets or sets a value that indicates what kind of selections are allowed.
        /// </summary>
        public TimelineSelectionMode SelectionMode
        {
            get { return (TimelineSelectionMode)GetValue(SelectionModeProperty); }
            set { SetValue(SelectionModeProperty, value); }
        }

        /// <summary>
        /// The selection mode property.
        /// </summary>
        public static readonly DependencyProperty SelectionModeProperty = DependencyProperty.Register(
            "SelectionMode",
            typeof(TimelineSelectionMode),
            typeof(MultiScaleTimeline),
            new PropertyMetadata(TimelineSelectionMode.SingleDate, OnSelectionModeChanged));

        /// <summary>
        /// Handle the SelectionMode property Changed.
        /// </summary>
        /// <param name="d">The Source object.</param>
        /// <param name="e">The event args.</param>
        private static void OnSelectionModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MultiScaleTimeline control = d as MultiScaleTimeline;
            TimelineSelectionMode value = (TimelineSelectionMode)e.NewValue;
            if (control != null)
            {
                control.OnSelectionModeChanged(value);
            }
        }

        private void OnSelectionModeChanged(TimelineSelectionMode value)
        {
            if (this.PlayButton != null)
            {
                this.PlayButton.IsEnabled = (value != TimelineSelectionMode.None);
            }
        }
        #endregion

        #region public YearOfDecade FirstYearOfDecade
        /// <summary>
        /// Gets or sets the start year of decade.
        /// </summary>
        public YearOfDecade FirstYearOfDecade
        {
            get { return (YearOfDecade)GetValue(FirstYearOfDecadeProperty); }
            set { SetValue(FirstYearOfDecadeProperty, value); }
        }

        /// <summary>
        /// The dependency property of FirstYearOfDecade.
        /// </summary>
        public static readonly DependencyProperty FirstYearOfDecadeProperty =
            DependencyProperty.Register("FirstYearOfDecade", typeof(YearOfDecade), typeof(MultiScaleTimeline), new PropertyMetadata(OnFirstYearOfDecadeChanged));

        /// <summary>
        /// Handle the FirstYearOfDecade property change.
        /// </summary>
        /// <param name="d">The source object.</param>
        /// <param name="e">The event args.</param>
        private static void OnFirstYearOfDecadeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MultiScaleTimeline control = d as MultiScaleTimeline;
            if (control != null)
            {
                control.UpdateVisual();
            }
        }

        #endregion

        #region public TimeSpan Interval

        /// <summary>
        /// Gets or sets the Interval of automatically changing selected date when user clicked the play button.
        /// </summary>
        public TimeSpan Interval
        {
            get { return (TimeSpan)GetValue(IntervalProperty); }
            set { SetValue(IntervalProperty, value); }
        }

        /// <summary>
        /// The Interval dependeny property.
        /// </summary>
        public static readonly DependencyProperty IntervalProperty =
            DependencyProperty.Register("Interval", typeof(TimeSpan), typeof(MultiScaleTimeline), new PropertyMetadata(new TimeSpan(0, 0, 1), OnIntervalChanged));

        /// <summary>
        /// Handle Interval dependency property changed.
        /// </summary>
        /// <param name="d">Event source Control.</param>
        /// <param name="e">Event args.</param>
        private static void OnIntervalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MultiScaleTimeline control = d as MultiScaleTimeline;
            TimeSpan value = (TimeSpan)e.NewValue;
            if (control != null && control.PlayTimer != null)
            {
                control.PlayTimer.Interval = value;
            }
        }

        #endregion

        /// <summary>
        /// Occurs when the DisplayDate property is changed. 
        /// </summary>
        public event EventHandler<DisplayDateChangedEventArgs> DisplayDateChanged;

        /// <summary>
        /// Occurs when the DisplayMode property is changed. 
        /// </summary>
        public event EventHandler<DisplayModeChangedEventArgs> DisplayModeChanged;

        /// <summary>
        /// Occurs when the SelectedDate property is changed. 
        /// </summary>
        public event EventHandler<SelectedDateChangedEventArgs> SelectedDatesChanged;

        /// <summary>
        /// Occurs when user click the play button to start automatically changing selected date.
        /// </summary>
        public event EventHandler StartPlay;

        /// <summary>
        /// Occurs when user click the stop button to stop automatically changing selected date or the selected date equals the SelectableEndDate.
        /// </summary>
        public event EventHandler StopPlay;

        /// <summary>
        /// Initializes a new instance of the MultiScaleTimeline class.
        /// </summary>
        public MultiScaleTimeline()
        {
            DefaultStyleKey = typeof(MultiScaleTimeline);
            this.TimelineMarks = new ObservableCollection<UIElement>();
        }

        public void Play()
        {
            if (this.PlayButton != null)
            {
                this.PlayButton.IsChecked = false;
            }
            RaiseStartPlay();
        }

        public void Stop()
        {
            if (this.PlayButton != null)
            {
                this.PlayButton.IsChecked = true;
            }
            RaiseStopPlay();
        }

        /// <summary>
        /// Invoked whenever application code or an internal process, such as a
        /// rebuilding layout pass, calls the ApplyTemplate method.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            this.PlayButton = GetTemplateChild(ElementPlayButton) as ToggleButton;
            this.TimelineItemPanel = GetTemplateChild(ElementTimelineItemPanel) as Grid;
            this.TimelineMarkPanel = GetTemplateChild(ElementTimelineMarkPanel) as Canvas;
            this.RollupButton = GetTemplateChild(ElementRollupButton) as Button;
            this.LeftButton = GetTemplateChild(ElementLeftButton) as RepeatButton;
            this.RightButton = GetTemplateChild(ElementRightButton) as RepeatButton;

            if (this.PlayButton != null)
            {
                this.PlayButton.Click += new RoutedEventHandler(OnButtonPlayClick);
            }

            if (this.TimelineMarkPanel != null)
            {
                this.TimelineMarkPanel.SizeChanged += new SizeChangedEventHandler(TimelineMarkPanel_SizeChanged);
                this.TimelineMarks.CollectionChanged += new NotifyCollectionChangedEventHandler(TimelineMarks_CollectionChanged);
            }

            if (this.RollupButton != null)
            {
                this.RollupButton.Click += RollupButton_Click;
            }

            if (this.LeftButton != null)
            {
                this.LeftButton.Click += LeftButton_Click;
            }

            if (this.RightButton != null)
            {
                this.RightButton.Click += RightButton_Click;
            }

            UpdateScale(Scale);

            // selected as initial date
            CurrentView.UpdateDate(SelectedDate);

            OnShowButtonPropertyChanged(this.ShowPlayButton);
            OnSelectionModeChanged(this.SelectionMode);

            PlayTimer = new DispatcherTimer();
            PlayTimer.Interval = this.Interval;
            PlayTimer.Tick += new EventHandler(OnTimerTicked);

            UpdateVisual();
        }

        /// <summary>
        /// Handle TimelineMarks' CollectionChanged Event
        /// </summary>
        /// <param name="sender">Event Source</param>
        /// <param name="e">Event args</param>
        private void TimelineMarks_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (TimelineMarkPanel == null)
            {
                return;
            }

            if (e.Action == NotifyCollectionChangedAction.Reset)
            {
                this.TimelineMarkPanel.Children.Clear();
                return;
            }

            if (e.OldItems != null)
            {
                foreach (UIElement item in e.OldItems)
                {
                    this.TimelineItemPanel.Children.Remove(item);
                }
            }

            if (e.NewItems != null)
            {
                foreach (UIElement item in e.NewItems)
                {
                    ArrangeMark(item, this.TimelineItemPanel.ActualWidth);
                }
            }
        }

        /// <summary>
        /// Handle TimelineMarks' SizeChanged Event
        /// </summary>
        /// <param name="sender">Event Source</param>
        /// <param name="e">Event args</param>
        private void TimelineMarkPanel_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            SetTimelineItemsVisible();

            ArrangeMarks();
        }

        /// <summary>
        /// Handle play button clicked event.
        /// </summary>
        /// <param name="sender">Event source.</param>
        /// <param name="e">Event args.</param>
        private void OnButtonPlayClick(object sender, RoutedEventArgs e)
        {
            // Check if button is pressed, if not means button is a play button then start playing.
            // If is pressed means button is a stop button then stop playing.
            if (PlayButton.IsChecked == false)
            {
                RaiseStartPlay();
                RaiseStartPlayEvent();
            }
            else
            {
                RaiseStopPlay();
                RaiseStopPlayEvent();
            }
        }

        /// <summary>
        /// Timer ticked event handler.
        /// </summary>
        /// <param name="sender">Event Source.</param>
        /// <param name="e">Event args.</param>
        private void OnTimerTicked(object sender, EventArgs e)
        {
            DateTime oldDate = this.SelectedDate;

            //// If fail to set, then stop playing, and reset the play button.
            if (!CurrentView.Next())
            {
                PlayButton.IsChecked = true;
                RaiseStopPlay();
                RaiseStopPlayEvent();
                return;
            }

            DateTime newDate = this.SelectedDate;

            RaiseSelectedDatesChanged(oldDate, newDate);
        }

        private void SetSelectionState()
        {
            if (CurrentView == null)
            {
                return;
            }

            //// Check if the new selected date is out of display
            //// if true, replace with new Timelineitems and fire DisplayDateChanged event.
            //Next Page
            //Previous Page
            int isOutofDisplay = this.CurrentView.IsSelectedDateOutOfDisplay;
            if (isOutofDisplay > 0)
            {
                CurrentView.GotoNextPage();
                UpdateVisual();
            }
            else if (isOutofDisplay < 0)
            {
                CurrentView.GotoPreviousPage();
                UpdateVisual();
            }

            //// set each Timelineitem's selection state
            if (this.TimelineItemPanel != null)
            {
                for (int i = 0; i < this.TimelineItemPanel.Children.Count; i++)
                {

                    TimelineItem item = this.TimelineItemPanel.Children[i] as TimelineItem;
                    item.CanSelect = this.CurrentView.IsItemSelectable(i);
                    item.CanDrilldown = this.CurrentView.TimelineItemCanDrilldown(i);
                    item.IsSelected = this.CurrentView.GetTimelineItemIsSelect(i);
                }
            }
        }

        /// <summary>
        /// Handle button rollup show previous.
        /// </summary>
        /// <param name="sender">Event source.</param>
        /// <param name="e">Event args.</param>
        private void LeftButton_Click(object sender, EventArgs e)
        {
            DateTime oldDateStart = CurrentView.BeginDate;
            DateTime oldDateEnd = CurrentView.EndDate;

            CurrentView.GotoPreviousPage();
            UpdateVisual();

            DateTime newDateStart = CurrentView.BeginDate;
            DateTime newDateEnd = CurrentView.EndDate;
            RaiseDisplayDateChangedEvent(oldDateStart, oldDateEnd, newDateStart, newDateEnd);
        }

        /// <summary>
        /// Handle rollup button show next.
        /// </summary>
        /// <param name="sender">Event source.</param>
        /// <param name="e">Event args.</param>
        private void RightButton_Click(object sender, EventArgs e)
        {
            DateTime oldDateStart = CurrentView.BeginDate;
            DateTime oldDateEnd = CurrentView.EndDate;

            CurrentView.GotoNextPage();
            UpdateVisual();

            DateTime newDateStart = CurrentView.BeginDate;
            DateTime newDateEnd = CurrentView.EndDate;
            RaiseDisplayDateChangedEvent(oldDateStart, oldDateEnd, newDateStart, newDateEnd);
        }

        /// <summary>
        /// Handle rollup button click text.
        /// </summary>
        /// <param name="sender">Event source.</param>
        /// <param name="e">Event args.</param>
        private void RollupButton_Click(object sender, EventArgs e)
        {
            TimelineScale oldScale = this.Scale;
            DateTime oldDateStart = CurrentView.BeginDate;
            DateTime oldDateEnd = CurrentView.EndDate;

            DateTime rollUpDate = CurrentView.ParentBeginDate;
            //// get current TimelineMode and set new TimelineMode
            switch (this.Scale)
            {
                case TimelineScale.Year:
                    this.Scale = TimelineScale.Decade;
                    break;
                case TimelineScale.Month:
                    this.Scale = TimelineScale.Year;
                    break;
                case TimelineScale.Day:
                    this.Scale = TimelineScale.Month;
                    break;
                case TimelineScale.Hour:
                    this.Scale = TimelineScale.Day;
                    break;
                case TimelineScale.Minute:
                    this.Scale = TimelineScale.Hour;
                    break;
                case TimelineScale.Second:
                    this.Scale = TimelineScale.Minute;
                    break;
            }

            CurrentView.UpdateDate(rollUpDate);
            UpdateVisual();

            TimelineScale newScale = this.Scale;
            RaiseScaleChangedEvent(oldScale, newScale);

            DateTime newDateStart = CurrentView.BeginDate;
            DateTime newDateEnd = CurrentView.EndDate;
            RaiseDisplayDateChangedEvent(oldDateStart, oldDateEnd, newDateStart, newDateEnd);
        }

        /// <summary>
        /// Item text clicked, timeline drill down detail scale view.
        /// </summary>
        /// <param name="sender">Event source.</param>
        /// <param name="e">Event args.</param>
        private void OnDrilldown(object sender, EventArgs e)
        {
            // On click text , will update Timeline mode, then update display date,
            // update timline items , finally fire DisplayModeChanged event and DisplayDateChanged event
            TimelineScale oldScale = this.Scale;
            DateTime oldDateStart = CurrentView.BeginDate;
            DateTime oldDateEnd = CurrentView.EndDate;

            TimelineItem item = sender as TimelineItem;
            switch (this.Scale)
            {
                case TimelineScale.Decade:
                    this.Scale = TimelineScale.Year;
                    break;
                case TimelineScale.Month:
                    this.Scale = TimelineScale.Day;
                    break;
                case TimelineScale.Year:
                    this.Scale = TimelineScale.Month;
                    break;
                case TimelineScale.Day:
                    this.Scale = TimelineScale.Hour;
                    break;
                case TimelineScale.Hour:
                    this.Scale = TimelineScale.Minute;
                    break;
                case TimelineScale.Minute:
                    this.Scale = TimelineScale.Second;
                    break;
            }

            CurrentView.UpdateDate(item.DateTime);
            UpdateVisual();

            TimelineScale newScale = this.Scale;
            RaiseScaleChangedEvent(oldScale, newScale);

            DateTime newDateStart = CurrentView.BeginDate;
            DateTime newDateEnd = CurrentView.EndDate;
            RaiseDisplayDateChangedEvent(oldDateStart, oldDateEnd, newDateStart, newDateEnd);
        }

        /// <summary>
        /// Timeline item selected.
        /// </summary>
        /// <param name="sender">Event source.</param>
        /// <param name="e">Event args.</param>
        private void OnItemSelected(object sender, EventArgs e)
        {
            DateTime oldDate = this.SelectedDate;
            TimelineItem item = sender as TimelineItem;
            this.SelectedDate = item.DateTime;
            DateTime newDate = this.SelectedDate;
            foreach (TimelineItem i in this.TimelineItemPanel.Children.OfType<TimelineItem>())
            {
                if (i.Equals(item))
                {
                    i.IsSelected = false;
                    i.IsSelected = true;
                }
                else
                {
                    i.IsSelected = true;
                    i.IsSelected = false;
                }
            }

            RaiseSelectedDatesChanged(oldDate, newDate);
        }

        /// <summary>
        /// Riase the event of DisplayModeChanged event.
        /// </summary>
        /// <param name="oldScale">Old TimelineScale.</param>
        /// <param name="newScale">New TimelineScale.</param>
        private void RaiseScaleChangedEvent(TimelineScale oldScale, TimelineScale newScale)
        {
            EventHandler<DisplayModeChangedEventArgs> handler = this.DisplayModeChanged;
            if (handler != null)
            {
                handler(this, new DisplayModeChangedEventArgs(oldScale, newScale));
            }
        }

        /// <summary>
        /// Riase the vent of DisplayDateChanged event.
        /// </summary>
        /// <param name="oldDateStart">Old Start Date.</param>
        /// <param name="oldDateEnd">Old End Date.</param>
        /// <param name="newDateStart">New Start Date.</param>
        /// <param name="newDateEnd">New End Date.</param>
        private void RaiseDisplayDateChangedEvent(DateTime oldDateStart, DateTime oldDateEnd, DateTime newDateStart, DateTime newDateEnd)
        {
            EventHandler<DisplayDateChangedEventArgs> handler = this.DisplayDateChanged;
            if (handler != null)
            {
                handler(this, new DisplayDateChangedEventArgs(oldDateStart, oldDateEnd, newDateStart, newDateEnd));
            }
        }

        /// <summary>
        /// Raise the event of play started.
        /// </summary>
        private void RaiseStartPlay()
        {
            PlayTimer.Start();

            //// When launch playing, Timeline items and Roll up button are not allowed to be clicked
            TimelineItemPanel.IsHitTestVisible = false;
            LeftButton.IsHitTestVisible = false;
            RightButton.IsHitTestVisible = false;
            RollupButton.IsHitTestVisible = false;

            //// Check if the selected date is not displayed in Timeline.
            //// if true, let selected date display in timeline.
            if (CurrentView.IsSelectedDateOutOfDisplay != 0)
            {
                DateTime oldDateStart = CurrentView.BeginDate;
                DateTime oldDateEnd = CurrentView.EndDate;

                CurrentView.UpdateDate(this.SelectedDate);
                UpdateVisual();

                DateTime newDateStart = CurrentView.BeginDate;
                DateTime newDateEnd = CurrentView.EndDate;

                RaiseDisplayDateChangedEvent(oldDateStart, oldDateEnd, newDateStart, newDateEnd);
            }
        }

        private void RaiseStartPlayEvent()
        {
            EventHandler handler = this.StartPlay;
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Stop the play animation and timer and raise the event of play stopped.
        /// </summary>
        private void RaiseStopPlay()
        {
            PlayTimer.Stop();

            // when autoplay is stopped, Timeline items and Rollup button can be clicked.
            TimelineItemPanel.IsHitTestVisible = true;

            this.LeftButton.IsHitTestVisible = true;
            this.RightButton.IsHitTestVisible = true;
            this.RollupButton.IsHitTestVisible = true;

        }

        private void RaiseStopPlayEvent()
        {
            EventHandler handler = this.StopPlay;
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Raise the event of selected date changed.
        /// </summary>
        /// <param name="oldDate">The old Date</param>
        /// <param name="newDate">The new Date</param>
        private void RaiseSelectedDatesChanged(DateTime oldDate, DateTime newDate)
        {
            EventHandler<SelectedDateChangedEventArgs> handler = SelectedDatesChanged;
            if (handler != null)
            {
                handler(this, new SelectedDateChangedEventArgs(oldDate, newDate));
            }
        }

        /// <summary>
        /// Refresh the UI.
        /// </summary>
        private void UpdateVisual()
        {
            // populate Timeline items
            if (TimelineItemPanel != null)
            {
                TimelineItemPanel.Children.Clear();
                int count = CurrentView.Items.Count();
                this.TimelineItemPanel.ColumnDefinitions.Clear();

                for (int i = 0; i < count; i++)
                {
                    TimelineItem item = this.CurrentView.PrepareTimelineItem(i);
                    item.Foreground = this.Foreground;

                    if (this.SelectionMode == TimelineSelectionMode.None)
                    {
                        item.CanSelect = false;
                    }

                    item.ClickText += new EventHandler(OnDrilldown);
                    item.ClickBar += new EventHandler(OnItemSelected);

                    Grid.SetColumn(item, i);

                    TimelineItemPanel.Children.Add(item);

                    // to set the Timelineitem's length to be average accuratly, use Grid's Layout System
                    TimelineItemPanel.ColumnDefinitions.Add(new ColumnDefinition());
                }

                SetTimelineItemsVisible();
            }

            // arrange Timeline marks
            ArrangeMarks();

            // update Rollup Control
            if (RollupButton != null)
            {
                this.RollupButton.Content = CurrentView.RollupText;
                this.RollupButton.IsEnabled = CurrentView.CanRollup;
                if (this.Scale == TimelineScale.Minute)
                {
                    ToolTipService.SetToolTip(this.RollupButton, CurrentView.BeginDate.ToString("MM/dd/yyyy h tt", CultureInfo.InvariantCulture));
                }
                else if (this.Scale == TimelineScale.Second)
                {
                    ToolTipService.SetToolTip(this.RollupButton, CurrentView.BeginDate.ToString("MM/dd/yyyy h:mm tt", CultureInfo.InvariantCulture));
                }
                else
                {
                    ToolTipService.SetToolTip(this.RollupButton, null);
                }
            }

            if (this.LeftButton != null)
            {
                this.LeftButton.IsEnabled = CurrentView.CanShowPrevious;
            }

            if (RightButton != null)
            {
                this.RightButton.IsEnabled = CurrentView.CanShowNext;
            }
        }

        /// <summary>
        /// Arrange TimelineMarks based on the Actual Width of TimelineMarkPanel.
        /// </summary>
        private void ArrangeMarks()
        {
            if (TimelineMarkPanel != null)
            {
                ArrangeMarks(this.TimelineMarkPanel.ActualWidth);
            }
        }

        /// <summary>
        /// Arrange TimelineMarks based on given width.
        /// </summary>
        /// <param name="markPanelWidth">Given width.</param>
        private void ArrangeMarks(double markPanelWidth)
        {
            if (this.TimelineMarkPanel != null)
            {
                if (this.TimelineMarks != null && this.TimelineMarks.Count > 0)
                {
                    foreach (UIElement item in this.TimelineMarks)
                    {
                        ArrangeMark(item, markPanelWidth);
                    }
                }
            }
        }

        /// <summary>
        /// Arrange the given TimelineMark based on the given Width.
        /// </summary>
        /// <param name="element">The Timeline Mark element</param>
        /// <param name="markPanelWidth">The given Width</param>
        private void ArrangeMark(UIElement item, double markPanelWidth)
        {
            DateTime markDate = (DateTime)item.GetValue(MultiScaleTimeline.MarkDateProperty);
            bool isOutOfDisplay = this.CurrentView.IsDateOutOfDisplay(markDate) != 0;
            if (isOutOfDisplay)
            {
                this.TimelineMarkPanel.Children.Remove(item);
                return;
            }

            if (null == VisualTreeHelper.GetParent(item))
            {
                this.TimelineMarkPanel.Children.Add(item);
            }

            double pos = CurrentView.GetTimelineMarkPosition(markDate, markPanelWidth);
            Canvas.SetLeft(item, pos);
        }

        /// <summary>
        /// Set the visible of TimelineItems based on the text actual Width and TimelineItem Width.
        /// </summary>
        private void SetTimelineItemsVisible()
        {
            if (this.TimelineItemPanel != null
                && this.TimelineItemPanel.Children != null
                && this.TimelineItemPanel.Children.Count > 0
                && this.TimelineItemPanel.ActualWidth > 0)
            {
                double itemWidth = this.TimelineItemPanel.ActualWidth / this.TimelineItemPanel.Children.Count;
                double maxWidth = double.MinValue;
                TextBlock model = new TextBlock();

                foreach (TimelineItem item in this.TimelineItemPanel.Children)
                {
                    if (item != null)
                    {
                        model.FontSize = item.FontSize;
                        model.FontFamily = item.FontFamily;
                        model.Text = item.Text;
                        maxWidth = Math.Max(model.ActualWidth, maxWidth);
                    }
                }

                if (maxWidth != double.MinValue)
                {
                    int i = 0;
                    foreach (TimelineItem item in this.TimelineItemPanel.Children)
                    {
                        if (itemWidth > maxWidth)
                        {
                            item.ShowText = true;
                            ToolTipService.SetToolTip(item, null);
                        }
                        else if (itemWidth > maxWidth - 4)
                        {
                            bool showText = (i & 1) == 0;
                            item.ShowText = showText;
                            ToolTipService.SetToolTip(item, showText == false ? item.Text : null);
                        }
                        else
                        {
                            item.ShowText = false;
                            ToolTipService.SetToolTip(item, item.Text);
                        }

                        i++;
                    }
                }
            }
        }

        /// <summary>
        /// Update timeline scale.
        /// </summary>
        /// <param name="newScale">New value.</param>
        private void UpdateScale(TimelineScale newScale)
        {
            CurrentView = TimeViewFactory.GetTimeView(this, newScale);
        }

        private void CheckSelectableDateRangeValide()
        {
            if (SelectableDateStart == null || SelectableDateEnd == null)
            {
                this.IsSelectableDateRangeValide = true;
            }

            this.IsSelectableDateRangeValide = SelectableDateStart <= SelectableDateEnd;
        }
    }
}
